/*
* Project: My Code
* Module: Kit File merge
* File: file_util.c
* Created Date: 2024-06-01 15:40:35
* Author: Huangjiacheng
* Description: description
*
*历史记录：
*
* Copyright (c) 2024 - vDiscovery, Inc
*/


/* ======================================================================================
* includes
* 添加头文件位置
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/stat.h>

#include "core.h"
#include "log.h"


/* ======================================================================================
* macros
* 宏定义位置
*/


/* ======================================================================================
* log
* log标志位置
*/


/* ======================================================================================
* extern
* 外部定义变量或函数位置
*/


/* ======================================================================================
* declaration
* 函数声明位置
*/


/* ======================================================================================
* types
* 类型定义位置
*/


/* ======================================================================================
* globals
* 全局变量位置
*/


/* ======================================================================================
* private implementation
* 本模块私有函数位置
*/

/**
 * @brief 读取文件的原始数据到buf里
 *
 * @param path
 * @param buf
 * @param file_size
 * @return int
 */
static int file_util_get_origin_file_data(char *path,unsigned char *buf,int file_size)
{
    int ret = 0;
    int fd = 0;
    char file_full_path[MAX_STR_LEN*2] = {0};

    util_merge_path_file_name(RES_DIR,path,file_full_path,MAX_STR_LEN*2);

    fd = open(file_full_path, O_RDONLY);
    if(fd <= 0)
    {
        log_error("打开%s文件失败\n",file_full_path);
        return -1;
    }

    ret = read(fd, buf, file_size);
    if(ret == file_size)
    {
        ret = 0;
    }
    else
    {
        log_error("%s文件读取失败\n",file_full_path);
        ret = -1;
    }

    close(fd);

    return ret;
}


/**
 * @brief 将读出的文件数据以追加方式输出到res.bin
 *
 * @param buf
 * @param file_size
 * @return int
 */
static int file_util_merge_file_data(unsigned char *buf, int file_size)
{
    int ret = 0;
    int fd = 0;

    char file_full_path[MAX_STR_LEN*2] = {0};

    util_merge_path_file_name(OUTPUT_DIR,RES_BIN_DATA,file_full_path,MAX_STR_LEN*2);

    fd = open(file_full_path, O_RDWR | O_CREAT | O_APPEND, S_IRWXU | S_IRWXG);
    if(fd <= 0)
    {
        log_error("%s文件打开失败\n",file_full_path);
        return -1;
    }

    ret = write(fd, buf, file_size);
    if (ret == file_size)
    {
        /*
            不延时会导致buf未写入的问题
        */
        usleep(10*1000);
        ret = 0;
    }
    else
    {
        log_error("res.bin文件写入长度错误 应写入%d  实际写入%d\n",file_size,ret);
        ret = -1;
    }

    close(fd);

    return ret;
}

/**
 * @brief 将读出的文件数据以追加方式输出到h文件
 * 
 */
static int file_util_output_h_file(files_t *file,int frist)
{
    int ret = 0;
    int fd = 0;
    int len = 0;
    char string_tmp[MAX_STR_LEN*10] = {0};
    char file_name[MAX_STR_LEN] = {0};
    char file_full_path[MAX_STR_LEN*2] = {0};

    util_merge_path_file_name(OUTPUT_DIR,C_CODE_H,file_full_path,MAX_STR_LEN*2);

    fd = open(file_full_path, O_RDWR | O_CREAT | O_APPEND, S_IRWXU | S_IRWXG);
    if(fd <= 0)
    {
        log_error("%s文件打开失败\n",file_full_path);
        return -1;
    }

    if( frist )
    {
        len += sprintf(string_tmp + len,"struct file_info\n{\n");
        len += sprintf(string_tmp + len,"    char *file_type;\n");
        len += sprintf(string_tmp + len,"    int offset;\n");
        len += sprintf(string_tmp + len,"    int length;\n};\n\n");
    }

    /* ======================================================================================
    * 判断文件名是否为数字
    * 如果文件名是数字则插入f_
    * ======================================================================================*/
    if( file->file_name[0] >= '0' && file->file_name[0] <= '9')
    {
        util_merge_path_file_name("f_",file->file_name,file_name,MAX_STR_LEN);
    }
    else
    {
        strcat(file_name,file->file_name);
    }
    util_replace_dot_with_underscore(file_name);

    len += sprintf(string_tmp + len,"const struct file_info %s = {\n",file_name);
    len += sprintf(string_tmp + len,"    .file_type = \"%s\",\n",file->file_type);
    len += sprintf(string_tmp + len,"    .offset = %d,\n",file->offset);
    len += sprintf(string_tmp + len,"    .length = %d \n};\n\n",file->length);

    ret = write(fd, string_tmp, len);
    if (ret == len)
    {
        /*
            不延时会导致buf未写入的问题
        */
        usleep(10*1000);
        ret = 0;
    }
    else
    {
        log_error("gen_res.h文件写入长度错误 应写入%d  实际写入%d\n",len,ret);
        ret = -1;
    }

    close(fd);

    return ret;
}

/* ======================================================================================
* implementation
* 本模块对外函数位置
*/

/**
 * @brief 读取旧资源文件的文件头信息
 *
 * @param bin_header
 * @return int
*/
int file_util_check_exist_res_bin(img_bin_header_info_t *bin_header)
{
    int fd = 0;
    int ret = 0;

    img_bin_header_info_t read_header;

    fd = open(RES_BIN, O_RDONLY);
    if(fd <= 0)
    {
        log_error("打开%s文件失败\n",RES_BIN);
        return -1;
    }

    ret = read(fd, &read_header, sizeof(img_bin_header_info_t));
    if(ret == sizeof(img_bin_header_info_t))
    {
        log_info("发现旧资源文件，旧资源文件版本 v%d \n", read_header.version);
        bin_header->version = read_header.version + 1;
        ret = 0;
    }
    else
    {
        log_error(" %s文件读取失败\n",RES_BIN);
        ret = -1;
    }

    return 0;
}

/**
 * @brief 判断路径的目录是否存在
 *
 * @param path
 * @return int
*/
int file_util_dir_exist(char *path)
{
    DIR *dir = opendir(path);
    if (dir)
    {
        closedir(dir);
    } else
    {
        log_error("%s目录不存在\n",path);
        return -1;
    }

    return 0;
}

/**
 * @brief 检查是否存在output目录，如果不存在则创建
 *
 * @return int
*/
int file_util_check_output_dir(void)
{
    struct stat output_dir_stat = {0};

    if( stat(OUTPUT_DIR, &output_dir_stat ) == -1)
    {
        log_info("目录%s 不存在，创建目录...\n",OUTPUT_DIR);
        if ( mkdir(OUTPUT_DIR, 0777) != 0 )
        {
            log_error("创建 %s 目录失败\n",OUTPUT_DIR);
            return -1;
        }
    }
    return 0;
}

/**
 * @brief 处理output目录内的文件
 *
 * @return int
*/
int file_util_output_dir_file_handle(void)
{
    char tmp_str[MAX_STR_LEN] = {0};

    if( util_merge_path_file_name(OUTPUT_DIR, RES_BIN_DATA, tmp_str, MAX_STR_LEN) == 0 )
    {
        if (access(tmp_str, F_OK) != -1)
        {
            log_info("发现%s 文件，删除...\n",tmp_str);
            remove(tmp_str);
            usleep(100*1000);
        }
    }


    if( util_merge_path_file_name(OUTPUT_DIR, C_CODE_H, tmp_str, MAX_STR_LEN) == 0 )
    {
        if (access(tmp_str, F_OK) != -1)
        {
            log_info("发现%s 文件，删除...\n",tmp_str);
            remove(tmp_str);
            usleep(100*1000);
        }
    }


    if( util_merge_path_file_name(OUTPUT_DIR, RES_BIN, tmp_str, MAX_STR_LEN) == 0 )
    {
        if (access(tmp_str, F_OK) != -1)
        {
            log_info("发现%s 文件，删除...\n",tmp_str);
            remove(tmp_str);
            usleep(100*1000);
        }
    }

    return 0;
}

/**
 * @brief 分析资源目录提取目录内的文件列表，如果存在指定后缀则提取指定后缀列表
 *
 * @param dir_info
 * @param file_suffix
 * @return int
*/
int file_util_catch_res_dir(dir_info_t *dir_info,char *file_suffix)
{
    DIR *sys_dir = NULL;
    struct dirent *sys_dirent = NULL;
    struct stat sys_file_info = {0};

    files_t *files = NULL;
    char tmp_str[MAX_STR_LEN] = {0};
    char *tmp = NULL;
    char file_full_path[MAX_STR_LEN*2] = {0};

    int i = 0;

    /* ======================================================================================
    * 打开res目录计算符合后缀名的文件
    * ======================================================================================*/
    sys_dir = opendir(RES_DIR);
    if(sys_dir == NULL)
    {
        log_error("打开%s目录失败\n",RES_DIR);
        return -1;
    }

    while(1)
    {
        sys_dirent = readdir(sys_dir);

        if(sys_dirent == NULL)
        {
            break;
        }

        if( strlen(file_suffix) > 1 && file_suffix[0] == '.' )
        {
            if(strstr(sys_dirent->d_name,file_suffix))
            {
                if(strlen(sys_dirent->d_name) > MAX_STR_LEN - 4)
                {
                    /*
                        文件名超过MAX_STR_LEN - 3则放弃，-3是要插入字符'.''/''\0'
                    */
                    log_warn("文件名字有个%ld 字超过 %d  跳过\n",strlen(sys_dirent->d_name),MAX_STR_LEN - 4);
                    continue;
                }
                i++;
            }
        }
        else
        {
            if( sys_dirent->d_name[0] == '.' )
            {
                /*
                    跳过目录下. 和 ..
                */
                continue;
            }

            if(strlen(sys_dirent->d_name) > MAX_STR_LEN - 4)
            {
                /*
                文件名超过MAX_STR_LEN - 3则放弃，-3是要插入字符'.''/''\0'
                */
                log_warn("文件名 %s 名字 %ld 字超过 %d  跳过\n",sys_dirent->d_name,strlen(sys_dirent->d_name),MAX_STR_LEN - 4);
                continue;
            }
            i++;
        }
    }
    closedir(sys_dir);

    /* ======================================================================================
    * 根据文件数量开辟对应的struct files_t内存
    * ======================================================================================*/
    dir_info->file_number = i;
    if( dir_info->file_number == 0)
    {
        log_error("%s目录未发现文件\n",RES_DIR);
        return -1;
    }

    files = (files_t *)malloc(dir_info->file_number * sizeof(files_t));
    if(files == NULL)
    {
        log_error("申请内存失败\n");
        return -1;
    }

    memset(files, 0, dir_info->file_number * sizeof(files_t));

    dir_info->file = files;

    /* ======================================================================================
    * 打开res目录，将文件信息保存到file_t内
    * ======================================================================================*/
    sys_dir = opendir(RES_DIR);
    if(sys_dir == NULL)
    {
        log_error("打开%s目录失败\n",RES_DIR);
        return -1;
    }

    i = 0;
    while (1)
    {
        sys_dirent = readdir(sys_dir);

        if(sys_dirent == NULL)
        {
            break;
        }

        if(strlen(file_suffix) > 1 && file_suffix[0] == '.' )
        {
            if(strstr(sys_dirent->d_name,file_suffix))
            {
                if(strlen(sys_dirent->d_name) < MAX_STR_LEN - 4)
                {
                    strcat(tmp_str, sys_dirent->d_name);
                    strcpy(files[i].file_name, tmp_str);
                }
                else
                {
                    log_warn("文件名 %s 超过 %d 字符长\n", sys_dirent->d_name, MAX_STR_LEN - 4);
                    memset(tmp_str, 0, MAX_STR_LEN);
                    continue;
                }

                /* ======================================================================================
                * 将带路径的文件名拼接起来用stat获取文件信息
                * ======================================================================================*/
                util_merge_path_file_name(RES_DIR,sys_dirent->d_name,file_full_path,MAX_STR_LEN);
                stat(file_full_path, &sys_file_info);

                /* ======================================================================================
                * 拷贝文件信息到file_t 类型变量
                * ======================================================================================*/
                strcpy(files[i].file_type,&file_suffix[1]);
                files[i].file_size = sys_file_info.st_size;
                files[i].length = sys_file_info.st_size;

                memset(file_full_path, 0, MAX_STR_LEN*2);
                memset(tmp_str, 0, MAX_STR_LEN);

                i++;
            }
        }
        else
        {
            if( sys_dirent->d_name[0] == '.' )
            {
                /*
                    跳过目录下. 和 ..
                */
                continue;
            }

            if(strlen(sys_dirent->d_name) < MAX_STR_LEN - 4)
            {
                strcat(tmp_str, sys_dirent->d_name);
                strcpy(files[i].file_name, tmp_str);
            }
            else
            {
                log_warn("文件名 %s 超过 %d 字符长\n",sys_dirent->d_name, MAX_STR_LEN - 4);
                memset(tmp_str, 0, MAX_STR_LEN);
                continue;
            }

            /* ======================================================================================
            * 将带路径的文件名拼接起来用stat获取文件信息
            * ======================================================================================*/
            util_merge_path_file_name(RES_DIR,sys_dirent->d_name,file_full_path,MAX_STR_LEN);
            stat(file_full_path, &sys_file_info);

            /* ======================================================================================
            * 拷贝文件信息到file_t 类型变量
            * ======================================================================================*/
            tmp = NULL;
            tmp = strrchr(sys_dirent->d_name, '.');
            if( tmp == NULL )
            {
                log_warn("文件名 %s 后缀有异常\n",sys_dirent->d_name);
                strcpy(files[i].file_type,"NULL");
            }
            else
            {
                strcpy(files[i].file_type,&tmp[1]);
            }
            files[i].file_size = sys_file_info.st_size;
            files[i].length = sys_file_info.st_size;

            memset(file_full_path, 0, MAX_STR_LEN * 2);
            memset(tmp_str, 0, MAX_STR_LEN);
            i++;
        }
    }
    closedir(sys_dir);

    dir_info->file_number = i;

    if(strlen(file_suffix) > 1 && file_suffix[0] == '.' )
    {
        log_info("搜索到%s后缀的文件有 %d 个\n", file_suffix,dir_info->file_number);
    }
    else
    {
        log_info("搜索到文件有 %d 个\n",dir_info->file_number);
    }

    printf("\n\n");
    printf("文件类型\t文件名\t\t\t文件大小:\n");
    for (i = 0; i < dir_info->file_number; i++)
    {
        printf("%s\t\t%s\t%d\n", dir_info->file[i].file_type,dir_info->file[i].file_name,dir_info->file[i].file_size);
    }
    printf("\n");

    return 0;
}

/**
 * @brief 根据g_dir_info记录的res目录文件情况生成纯文件数据的res_data.bin和描述data文件的h文件
 * 
 * @param dir 
 * @return int 
 */
int file_util_gen_res_data(dir_info_t *dir)
{
    int i = 0;
    int ret = 0;
    int file_size = 0;
    char *buf = NULL;
    files_t *current_file = NULL;
    files_t *last_file = NULL;

    for ( i = 0; i < dir->file_number;i++ )
    {

        current_file = &(dir->file[i]);

        /*
            offset值赋值，第0个offset为0所以不赋值，0值来源上面对结构体初始化
        */
        if(i > 0)
        {
            last_file =  &(dir->file[i - 1]);
            current_file->offset += last_file->offset + last_file->length;
        }

        /*
            开辟读的文件的内存空间
        */
        file_size = current_file->file_size;

        buf = malloc(file_size);
        if (buf == NULL)
        {
            return -1;
        }
        memset(buf, 0, file_size);

        /*
            读取文件将整个文件赋值到空间buf里
        */
        ret = file_util_get_origin_file_data(current_file->file_name, buf, file_size);
        if (ret)
        {
            log_error("获取资源 %s 文件数据失败 \n",current_file->file_name);
            free(buf);
            buf = NULL;
            break;
        }

        /*
            将buf以追加文件方式写入到文件内
        */
        ret = file_util_merge_file_data(buf,file_size);
        if (ret)
        {
            log_error("输出 %s 文件失败 \n",RES_BIN_DATA);
            free(buf);
            buf = NULL;
            break;
        }

        ret = file_util_output_h_file(current_file,!i);
        if (ret)
        {
            log_error("输出 %s 文件失败 \n",C_CODE_H);
            free(buf);
            buf = NULL;
            break;
        }
        printf("%s\toffset %d\tlength %d\n", current_file->file_name,current_file->offset, current_file->length);

        free(buf);
        buf = NULL;
    }

    return 0;
}

/**
 * @brief 根据g_dir_info记录的res目录文件情况生成纯文件数据的res_data.bin和描述data文件的h文件
 * 
 * @param dir 
 * @return int 
 */
int file_util_gen_single_res_bin(dir_info_t *dir,img_bin_header_info_t *header)
{
    int i = 0;
    img_res_info_t *img_res_info;

    struct stat data_file_info = {0};
    files_t *file_info = dir->file;
    int size = 0;
    int fd = 0;
    int ret;
    void *data_file = NULL;
    char out_file[MAX_STR_LEN*2] = {0};

    if( file_info == NULL )
    {
        log_error("files_t类型获取失败 \n");
        return -1;
    }

    if( dir->file_number == 0 )
    {
        log_warn("res目录没有所需文件 \n");
        return 0;
    }

    img_res_info = (img_res_info_t *)malloc(sizeof(img_res_info_t) * dir->file_number);
    if (img_res_info == NULL)
    {
        log_error("img_res_info_t类型内存申请失败 \n");
        return -1;
    }

    memset(img_res_info, 0x00, sizeof(img_res_info_t) * dir->file_number);

    for (i = 0; i < dir->file_number; i++)
    {
        img_res_info[i].offset = file_info[i].offset;
        img_res_info[i].size = file_info[i].length;
        size = strlen(file_info[i].file_name);

        if (size > VD_KIT_ID_LEN - 1)
        {
            log_warn("文件名超长 当前长度 %d \n",size);
            size = VD_KIT_ID_LEN - 1;
        }
        memcpy(img_res_info[i].id, file_info[i].file_name,size);

        util_replace_dot_with_underscore(img_res_info[i].id);

        strcpy(img_res_info[i].src, file_info[i].file_type);
    }

    /*
        读取bin data 大小
    */
    util_merge_path_file_name(OUTPUT_DIR,RES_BIN_DATA,out_file,MAX_STR_LEN*2);
    stat(out_file, &data_file_info);
    
    /*
        准备bin data 数据
    */
    if( data_file_info.st_size <= 0 )
    {
        log_error("未获得res_data.bin数据\n");
        return -1;
    }

    data_file = malloc(data_file_info.st_size);
    
    fd = open(out_file, O_RDONLY);
    if(fd <= 0)
    {
        log_error("打开%s文件失败\n",out_file);
        goto exit;
    }

    ret = read(fd, data_file, data_file_info.st_size);
    if(ret == data_file_info.st_size)
    {
        ret = 0;
    }
    else
    {
        log_error("%s文件读取失败\n",out_file);
        goto exit;
    }

    close(fd);
    fd = 0;


    if( header->version == 0 )
    {
        header->version = 1;
        log_warn("未定义资源版本，默认设为 v%d\n",header->version);
    }
    header->resoucrce_num = dir->file_number;
    header->resoucrce_size = data_file_info.st_size;

    util_merge_path_file_name(OUTPUT_DIR,RES_BIN,out_file,MAX_STR_LEN*2);

    fd = open(out_file, O_RDWR | O_CREAT | O_APPEND, S_IRWXU | S_IRWXG);
    if(fd <= 0)
    {
        log_error("%s文件打开失败\n",out_file);
        goto exit;
    }

    /*
        写入文件头数据
    */
    ret = write(fd, header, sizeof(img_bin_header_info_t) );
    if (ret == sizeof(img_bin_header_info_t) )
    {
        /*
            不延时会导致buf未写入的问题
        */
        usleep(10*1000);
        ret = 0;
    }
    else
    {
        log_error("res.bin文件写入长度错误 应写入%ld  实际写入%d\n",sizeof(img_bin_header_info_t),ret);
        goto exit;
    }

    /*
        写入文件数据段
    */
    ret = write(fd, data_file, data_file_info.st_size);
    if (ret == data_file_info.st_size)
    {
        /*
            不延时会导致buf未写入的问题
        */
        usleep(10*1000);
        ret = 0;
    }
    else
    {
        log_error("res.bin文件写入长度错误 应写入%ld  实际写入%d\n",data_file_info.st_size,ret);
        goto exit;
    }

    /*
        写入12字节空数据用于隔断文件描述段
    */
    memset(out_file, 0xFF, MAX_STR_LEN*2);

    ret = write(fd, out_file, 12);
    if (ret == 12)
    {
        /*
            不延时会导致buf未写入的问题
        */
        usleep(10*1000);
        ret = 0;
    }
    else
    {
        log_error("res.bin文件写入长度错误 应写入12  实际写入%d\n",ret);
        goto exit;
    }

    /*
        写入文件描述段
    */
    ret = write(fd, img_res_info, sizeof(img_res_info_t) * dir->file_number);
    if (ret == sizeof(img_res_info_t) * dir->file_number)
    {
        /*
            不延时会导致buf未写入的问题
        */
        usleep(10*1000);
        ret = 0;
    }
    else
    {
        log_error("res.bin文件写入长度错误 应写入%ld  实际写入%d\n",sizeof(img_res_info_t) * dir->file_number,ret);
        ret = -1;
    }

    close(fd);
    return 0;

exit:
    if(data_file)
    {
        free(data_file);
        data_file = NULL;
    }

    if(fd)
    {
        close(fd);
        fd = 0;
    }

    return -1;
}